package com.tianji.contract.service.Impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.constant.ExcelConstants;
import com.tianji.common.utils.JDBCDruidUtils;
import com.tianji.contract.mapper.EmpMapper;
import com.tianji.contract.pojo.Emp;
import com.tianji.contract.pojo.vo.EmpVo;
import com.tianji.contract.service.EmpService;
import com.tianji.coupon.mapper.CouponScopeMapper;
import com.tianji.coupon.pojo.CouponScope;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Service
@RequiredArgsConstructor
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {
    private final EmpMapper empMapper;
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    @Override
    public void export() throws IOException {
        OutputStream outputStream = null;
        // 记录每个Sheet的错误信息
        Map<Integer, String> errorLogs = new ConcurrentHashMap<>();
        try {
            Long totalCountLong = empMapper.selectCount(null);
            Integer totalCount = Math.toIntExact(totalCountLong);
            // 每个Sheet最大行数
            Integer sheetDataRows = ExcelConstants.PER_SHEET_ROW_COUNT;
            // 每次分页查询的数据量
            Integer writeDataRows = ExcelConstants.PER_WRITE_ROW_COUNT;
            // 计算需要的Sheet数量
            Integer sheetNum = (int) Math.ceil((double) totalCount / sheetDataRows);
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletResponse response = requestAttributes.getResponse();
            outputStream = response.getOutputStream();
            ExcelWriter excelWriter = EasyExcel.write(outputStream).build();
            // 用于等待所有线程完成
            CountDownLatch latch = new CountDownLatch(sheetNum);

            for (int i = 0; i < sheetNum; i++) {
                // 使用final保证线程内可访问
                final int sheetIndex = i;
                executorService.submit(() -> {
                    try {
                        WriteSheet writeSheet = EasyExcel.writerSheet(sheetIndex, "员工信息" + (sheetIndex + 1))
                                .head(EmpVo.class)
                                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                                .build();

                        int remainingRows = totalCount - (sheetIndex * sheetDataRows); // 剩余数据行数
                        int currentSheetRowCount = Math.min(remainingRows, sheetDataRows); // 当前Sheet需要写入的总行数
                        int writeCount = (int) Math.ceil((double) currentSheetRowCount / writeDataRows); // 当前Sheet的写入次数

                        for (int j = 0; j < writeCount; j++) {
                            try {
                                int pageNumber = j + 1 + (sheetIndex * (sheetDataRows / writeDataRows));
                                Page<Emp> page = empMapper.selectPage(
                                        new Page<>(pageNumber, writeDataRows), null);
                                List<Emp> empList = page.getRecords();
                                List<EmpVo> empVoList = new ArrayList<>();

                                for (Emp emp : empList) {
                                    try {
                                        EmpVo empVo = new EmpVo();
                                        BeanUtils.copyProperties(emp, empVo); // 复制属性
                                        empVoList.add(empVo);
                                    } catch (Exception e) {
                                        errorLogs.put(sheetIndex, "数据转换异常: " + emp.getEmpno() + " - " + e.getMessage());
                                    }
                                }

                                excelWriter.write(empVoList, writeSheet); // 写数据

                            } catch (Exception e) {
                                errorLogs.put(sheetIndex, "查询或写入异常: 第" + j + "次查询 - " + e.getMessage());
                            }
                        }

                    } catch (Exception e) {
                        errorLogs.put(sheetIndex, "Sheet创建或写入异常: " + e.getMessage());
                    } finally {
                        latch.countDown(); // 当前线程完成，计数器减一
                    }
                });
            }

            latch.await(); // 等待所有线程完成

            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            String fileName = URLEncoder.encode("员工信息", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

            excelWriter.finish();
            outputStream.flush();
            // 输出错误日志
            if (!errorLogs.isEmpty()) {
                System.err.println("导出过程中发生了错误:");
                errorLogs.forEach((sheetIndex, error) -> {
                    System.err.println("Sheet " + sheetIndex + ": " + error);
                });
            }

        } catch (IOException | BeansException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                outputStream.close(); // 关闭输出流
            }
            executorService.shutdown(); // 关闭线程池
        }
    }

    /*
     * 测试用Excel导入超过10w条数据,经过测试发现,使用Mybatis的批量插入速度非常慢,所以这里可以使用 数据分批+JDBC分批插入+事务来继续插入速度会非常快
     */
    @Override
    public void importData(List<Map<Integer, String>> dataList) {
        //结果集中数据为0时,结束方法.进行下一次调用
        if (dataList.size() == 0) {
            return;
        }
        //JDBC分批插入+事务操作完成对20w数据的插入
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            long startTime = System.currentTimeMillis();
            System.out.println(dataList.size() + "条,开始导入到数据库时间:" + startTime + "ms");
            conn = JDBCDruidUtils.getConnection();
            //控制事务:默认不提交
            conn.setAutoCommit(false);
            String sql = "insert into emp (`empno`, `ename`, `job`, `mgr`, `hiredate`, `sal`, `comm`, `deptno`) values";
            sql += "(?,?,?,?,?,?,?,?)";
            ps = conn.prepareStatement(sql);
            //循环结果集:这里循环不支持lambda表达式
            for (int i = 0; i < dataList.size(); i++) {
                Map<Integer, String> item = dataList.get(i);
                ps.setString(1, item.get(0));
                ps.setString(2, item.get(1));
                ps.setString(3, item.get(2));
                ps.setString(4, item.get(3));
                ps.setString(5, item.get(4));
                ps.setString(6, item.get(5));
                ps.setString(7, item.get(6));
                ps.setString(8, item.get(7));
                //将一组参数添加到此 PreparedStatement 对象的批处理命令中。
                ps.addBatch();
            }
            //执行批处理
            ps.executeBatch();
            //手动提交事务
            conn.commit();
            long endTime = System.currentTimeMillis();
            System.out.println(dataList.size() + "条,结束导入到数据库时间:" + endTime + "ms");
            System.out.println(dataList.size() + "条,导入用时:" + (endTime - startTime) + "ms");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关连接
            JDBCDruidUtils.close(conn, ps);
        }
    }



}
